##############################################################################
##  IPPI :: IP Printer Installer
##
##  Is used to install a printer on a Windows XP machine using standard
##  TCP/IP printing.  It accepts command line arguments that will install
##  a specific printer with the options given on the commandline.
##  If no options are given the GUI mode will start.  The GUI mode reads
##  a file called printers.ini that specifies which printers should be listed
##  and settings to use when installing the printer.
##
##  The method used here is developed from the documentation for installing
##  a local printer automatcially at:
##  https://engineering.purdue.edu/ECN/Support/KnowledgeBase/Docs/20040216090320
##
##  IPPI was written using Python 2.5.  It was also compiled to an exe using
##  Py2exe 0.6.6 (http://www.py2exe.org/).
##
##  The graphical user interface uses the easygui (http://www.ferg.org/easygui/)
##  library.
##
##  May 8, 2007
##  Tyler Stransky
##  tyler.stransky@gmail.com
################################################################################


# _winreg is for modifying the Windows Registry
# Used in createPrinterPort
import _winreg as wreg

# OptionParser is used to accept command line arguments
from optparse import OptionParser

# ConfigParser is used to read the printers.ini file
import ConfigParser

# easygui is avliable from: http://www.ferg.org/easygui/
# It provides an easy way to show the choicebox in the main function
from easygui import *

# Basics for communicating with the computer
import sys, os, shutil

#options = ["IPAddress", "PrinterName", "DriverName", "DriverLocation"]
defaultDriverLocation = "%windir%\\inf\\ntprint.inf"

verbose = False
settingsDirectory = os.getcwd() + os.path.sep + "settings"
printersIni = "printers.ini"

def getPrinterSettings(Printer, ini):
    # Make sure that the printer has the required settings specified
    checkOption(Printer, "IPAddress", ini)
    checkOption(Printer, "PrinterName", ini)
    checkOption(Printer, "DriverName", ini)

    # Create the return object
    settings = {}
    
    settings['IPAddress'] = ini.get(Printer, 'IPAddress')
    settings['PrinterName'] = ini.get(Printer, 'PrinterName')
    settings['DriverName'] = ini.get(Printer, 'DriverName')

    # Check for optional configuration options in the ini file.
    if ini.has_option(Printer, "DriverLocation"):
        settings['DriverLocation'] = ini.get(Printer, "DriverLocation")
    else:
        # No driver location was specified so we use the default driver location.
        settings['DriverLocation'] = defaultDriverLocation
        
    if ini.has_option(Printer, "settings"):
        settings['settings'] = ini.get(Printer, "Settings")
    else:
        # No settings option was specified.
        settings['settings'] = None
        
    return settings

# Make sure that the option is present for the specified printer in the ini file.
def checkOption(printer, option, ini):
    if ini.has_option(printer, option)==False:
        print "Printer '" + Printer + "' is missing the " + option + " option."
        sys.exit(1)        

# createPrinterPort modifies the registry to add a Standard TCP/IP port pointing to 
# IPAddress with the standard windows TCP/IP port name of IP_$IPAddress
def createPrinterPort (IPAddress):
    Port = wreg.CreateKey(
        wreg.HKEY_LOCAL_MACHINE,
        "SYSTEM\CurrentControlSet\Control\Print\Monitors\Standard TCP/IP Port\Ports\IP_" + IPAddress)
    wreg.SetValueEx(Port, "HostName", 0, wreg.REG_SZ, "")
    wreg.SetValueEx(Port, "IPAddress", 0, wreg.REG_SZ, IPAddress)
    wreg.SetValueEx(Port, "HWAddress", 0, wreg.REG_SZ, "")
    wreg.SetValueEx(Port, "Protocol", 0, wreg.REG_DWORD, 1)
    wreg.SetValueEx(Port, "Version", 0, wreg.REG_DWORD, 1)
    wreg.SetValueEx(Port, "SNMP Enabled", 0, wreg.REG_DWORD, 1)
    wreg.SetValueEx(Port, "SNMP Index", 0, wreg.REG_DWORD, 1)
    wreg.SetValueEx(Port, "PortNumber", 0, wreg.REG_DWORD, 9100)
    wreg.SetValueEx(Port, "SNMP Community", 0, wreg.REG_SZ, "public")

# Issues the commands to stop and then start the print spooler
def restartPrintSpooler():
    runCommand("net stop spooler")
    runCommand("net start spooler")

# Removes a printer with PrinterName from the system
# It is necessary to make sure that no printer exists before trying to
# add it to the system.
def deletePrinter(PrinterName):
    delCommand = "rundll32 printui.dll,PrintUIEntry /dl /n \"" + PrinterName + "\" /q"
    runCommand(delCommand)

# createIPPrinter issues the command to install a new printer
def createIPPrinter(IPAddress, PrinterName, DriverName, DriverLocation):
    addCommand = "rundll32 printui.dll,PrintUIEntry /if /b \"" + PrinterName + "\" /f \"" + DriverLocation + "\" /r \"IP_" + IPAddress + "\" /m \"" + DriverName + "\" /z /u"
    runCommand(addCommand)

# configurePrinterSettings issues the Windows command to read the saved settings file and configure the installed printer accordingly.
def configurePrinterSettings(PrinterName, SettingsFile):
    runCommand("rundll32 printui.dll,PrintUIEntry /Sr /n \"" + PrinterName + "\" /a \"" + SettingsFile + "\" d")
    
def runCommand(command):
    if verbose==True:
        print command
    os.system(command)

# installPrinter calls each of the individual functions in the order necessary
# to actually get the printer installed.
def installPrinter(IPAddress, PrinterName, DriverName, DriverLocation, Settings=None):
    print "Creating IP Printer Port..."
    createPrinterPort(IPAddress)

    print "Restarting the print spooler..."
    restartPrintSpooler()

    print "Cleaning out previous printer..."
    deletePrinter(PrinterName)

    print "Installing printer and drivers..."
    createIPPrinter(IPAddress, PrinterName, DriverName, DriverLocation)
    
    if Settings!=None:
        print "Configuring printer settings..."
        configurePrinterSettings(PrinterName, Settings)

    print "All done."

def addPrinterIni():
    title = "Add Printer - IP Printer Installer" 
    message = "Please describe the printer you wish to add.\nPrinter driver information will be collected next."
    fieldNames = ["Section Title", "Printer Name", "IP Address"]
    fieldValues = []

    fieldValues = multenterbox(message, title, fieldNames)

    while 1:
        if fieldValues == None: sys.exit()
        errmsg = ""
        for i in range(len(fieldNames)):
                if fieldValues[i].strip() == "":
                        errmsg = errmsg + ('"%s" is a required field.\n\n' % fieldNames[i])
        if errmsg == "": break # no problems found
        fieldValues = multenterbox(errmsg, title, fieldNames, fieldValues)

    newSectionName = fieldValues[0]
    newPrinterName = fieldValues[1]
    newIPAddress = fieldValues[2]
    
    message = "Select the printer .inf file in the printer driver for this printer"
    title = ".inf File Selection - IP Printer Installer"

    newPrinterDriver = fileopenbox(message, title)
    if newPrinterDriver==None:
        msgbox("No file selected.\nExiting")
        sys.exit()

    inf = ConfigParser.SafeConfigParser()

    try:
        inf.read([newPrinterDriver])
    except IOError:
        print "Could not read printer .inf file '" + newPrinterDriver + "'\nExiting"
        sys.exit(1)

    man = inf.options(MANUFACTURER)
    print man
    
    
def updatePrinterSettings():

    print "Backing up " + printersIni + "..."
    shutil.copy(printersIni, printersIni + ".backup")

    ini = ConfigParser.SafeConfigParser()
    
    try:
        ini.read([printersIni])
    except IOError:
        print "Could not open 'printers.ini'"
        sys.exit(1)

    # Each printer is specified in its own section in the ini file
    printers = ini.sections()

    # Display the list of printers to the user.
    choice = choicebox("Select a printer to store settings.\n\nNote: The printer must already be installed and configured on this computer.",
                        "KGS IP Printer Installer", printers)

    # if choice = None then the user clicked cancel or didn't make a selection
    if choice != None:
        settings = getPrinterSettings(choice, ini)
        PrinterName = settings['PrinterName']
    else:
        sys.exit()
    
    print "Checking the settings directory..."
    # Check to see if the settings directory exists
    if os.path.exists(settingsDirectory)==False:
        # If not, try to create it
        try:
            os.mkdir(settingsDirectory)
        except IOError:
            print "Error making settings directory.\nExiting!"
            sys.exit(1)
    
    # file name for the printer settings dat file
    datFile = settingsDirectory + os.path.sep + choice + ".dat"
    
    print "Creating the printer settings file (" + datFile + ")..."
    # create the printer settings dat file
    os.system("rundll32 printui.dll,PrintUIEntry /Ss /n \"" + PrinterName + "\" /a \"" + datFile + "\" d")
    
    print "Modifying the printers.ini file..."
    # Modify the ini file with the saved settings.
    ini.set(choice, "settings", datFile)
    
    print "Writing the " + printersIni + " file..."
    try:
        iniFile = open(printersIni, "wb")
        ini.write(iniFile)
        iniFile.close()
    except IOError:
        print "Failed to write updated " + printersIni + " file.\nExiting!"
        sys.exit(1)
    
    print "All done."
    

###############################################################
## Main Program
###############################################################

# OptionParser is the object that parses the command line options
parser = OptionParser()

# .add_option instructs the parse how to parse the command line
# options.  See the Python documentation for more information
parser.add_option("-i", "--IPAddress", dest="IPAddress",
                  help="IP address of the printer to be installed")

parser.add_option("-n", "--PrinterName", dest="PrinterName",
                  help="Name to be given to the newly installed printer.  Any existing printer with this name will be deleted.")

parser.add_option("-d", "--DriverName", dest="DriverName",
                  help="Driver name in the inf file used by the printer driver.")

parser.add_option("-l", "--DriverLocation", dest="DriverLocation",
                  help="Optional location of printer driver .inf file.  If left out the windows default printer .inf will be used.")

parser.add_option("-f", "--DatFile", dest="SettingsLocation",
                  help="Optional dat file with printer settings.  Settings files can be created using the -s option or manually with the printui.dll")

parser.add_option("-a", "--AddPrinter", dest="AddPrinter", default=False, action="store_true",
                  help="A graphical mode to add a printer to the printers.ini file.")

parser.add_option("-s", "--SaveSettings", dest="Settings", default=False, action="store_true",
                  help="Extracts printer specific settings (duplexer, paper trays, etc...) from an installed printer, stores the settings .dat file in a directory called settings and edits the printers.ini file with the Settings option.")

parser.add_option("-v", "--Verbose", dest="Verbose", default=False, action="store_true",
                  help="Turns on verbose mode.")


# Extract the options and arguments from the parser
(options, args) = parser.parse_args()


# Check options

verbose = options.Verbose

if options.AddPrinter==True:
    # AddPrinter mode was selected
    addPrinterIni()

elif options.Settings==True:
    # Update printer specific settings mode was selected.
    updatePrinterSettings()
    
elif (options.IPAddress==None) and (options.PrinterName==None) and (options.DriverName==None) and (options.DriverLocation==None) and (options.SettingsLocation==None):
    # Enter GUI Printer Install Mode

    # ConfigParser is the object that read the .ini file that describes the printers
    # that will be listed.
    ini = ConfigParser.SafeConfigParser()

    print "Reading printer description file..."
    
    try:
        ini.read([printersIni])
    except IOError:
        print "Could not open '" + printersIni + "'"
        sys.exit(1)

    os.chdir(os.environ['TMP'])

    # Each printer is specified in its own section in the ini file
    printers = ini.sections()

    # Display the list of printers to the user.
    choice = choicebox("Please select a printer to install", "IP Printer Installer", printers)

    # if choice = None then the user clicked cancel or didn't make a selection
    if choice != None:
        settings = getPrinterSettings(choice, ini)
        if verbose==True:
            print choice + " selected."
            print printersIni + " settings are:"
            print settings
            print ""
        IPAddress = settings['IPAddress']
        PrinterName = settings['PrinterName']
        DriverName = settings['DriverName']
        DriverLocation = settings['DriverLocation']
        SettingsLocation = settings['settings']

        # Now that all the data has been appropriated we can actually get on with
        # installing the printer.
        installPrinter(IPAddress, PrinterName, DriverName, DriverLocation, SettingsLocation)

   
elif (options.IPAddress!=None) or (options.PrinterName!=None) or (options.DriverName!=None):
    # All command line options were provided
    IPAddress = options.IPAddress
    PrinterName = options.PrinterName
    DriverName = options.DriverName
    if options.DriverLocation==None:
        DriverLocation = defaultDriverLocation
    else:
        DriverLocation = options.DriverLocation
        
    SettingsLocation = options.SettingsLocation
    
    os.chdir(os.environ['TMP'])
    installPrinter(IPAddress, PrinterName, DriverName, DriverLocation, SettingsLocation)

else:
    # Some command line options were provided but, not all the required options were specified.
    print "Not all required options were specified."
    parser.print_help()
    sys.exit(2)